package com.leo.tool.jdk.core;

import lombok.SneakyThrows;
import org.junit.Assert;
import org.junit.Test;

import java.lang.Thread.State;
import java.util.Arrays;
import java.util.List;

public class ThreadStateTest {

    @Test
    public void testNotify() throws InterruptedException {
        Thread thread_0 = new Thread(ThreadStateTest::run);
        Thread thread_1 = new Thread(ThreadStateTest::run);
        Thread thread_2 = new Thread(ThreadStateTest::run);
        List<Thread> threads = Arrays.asList(thread_0, thread_1, thread_2);
        Assert.assertEquals(3, threads.stream().map(Thread::getState).filter(State.NEW::equals).count());
        printThreadStates(threads);
        threads.forEach(Thread::start);
        Thread.sleep(5);
        Assert.assertEquals(3, threads.stream().map(Thread::getState).filter(State.WAITING::equals).count());
        printThreadStates(threads);
        synchronized (ThreadStateTest.class) {
            ThreadStateTest.class.notify();
            Assert.assertEquals(2, threads.stream().map(Thread::getState).filter(State.WAITING::equals).count());
            Assert.assertEquals(1, threads.stream().map(Thread::getState).filter(State.BLOCKED::equals).count());
            printThreadStates(threads);
        }
        Thread.sleep(5);
        synchronized (ThreadStateTest.class) {
            ThreadStateTest.class.notify();
            Assert.assertEquals(1, threads.stream().map(Thread::getState).filter(State.WAITING::equals).count());
            Assert.assertEquals(1, threads.stream().map(Thread::getState).filter(State.BLOCKED::equals).count());
            Assert.assertEquals(1, threads.stream().map(Thread::getState).filter(State.TERMINATED::equals).count());
            printThreadStates(threads);
        }
        Thread.sleep(5);
        synchronized (ThreadStateTest.class) {
            ThreadStateTest.class.notify();
            Assert.assertEquals(0, threads.stream().map(Thread::getState).filter(State.WAITING::equals).count());
            Assert.assertEquals(1, threads.stream().map(Thread::getState).filter(State.BLOCKED::equals).count());
            Assert.assertEquals(2, threads.stream().map(Thread::getState).filter(State.TERMINATED::equals).count());
            printThreadStates(threads);
        }
        Thread.sleep(5);
        Assert.assertEquals(0, threads.stream().map(Thread::getState).filter(State.WAITING::equals).count());
        Assert.assertEquals(0, threads.stream().map(Thread::getState).filter(State.BLOCKED::equals).count());
        Assert.assertEquals(3, threads.stream().map(Thread::getState).filter(State.TERMINATED::equals).count());
        printThreadStates(threads);
    }

    @Test
    public void testNotifyAll() throws InterruptedException {
        Thread thread_0 = new Thread(ThreadStateTest::run);
        Thread thread_1 = new Thread(ThreadStateTest::run);
        Thread thread_2 = new Thread(ThreadStateTest::run);
        List<Thread> threads = Arrays.asList(thread_0, thread_1, thread_2);
        Assert.assertEquals(3, threads.stream().map(Thread::getState).filter(State.NEW::equals).count());
        printThreadStates(threads);
        threads.forEach(Thread::start);
        Thread.sleep(5);
        Assert.assertEquals(3, threads.stream().map(Thread::getState).filter(State.WAITING::equals).count());
        printThreadStates(threads);
        synchronized (ThreadStateTest.class) {
            ThreadStateTest.class.notifyAll();
            Assert.assertEquals(0, threads.stream().map(Thread::getState).filter(State.WAITING::equals).count());
            Assert.assertEquals(3, threads.stream().map(Thread::getState).filter(State.BLOCKED::equals).count());
            printThreadStates(threads);
        }
        Thread.sleep(5);
        Assert.assertEquals(0, threads.stream().map(Thread::getState).filter(State.WAITING::equals).count());
        Assert.assertEquals(0, threads.stream().map(Thread::getState).filter(State.BLOCKED::equals).count());
        Assert.assertEquals(3, threads.stream().map(Thread::getState).filter(State.TERMINATED::equals).count());
        printThreadStates(threads);
    }

    @Test
    public void testTimedWait_release_by_itself() throws InterruptedException {
        Thread thread = new Thread(ThreadStateTest::timedWait);
        thread.start();
        Thread.sleep(5);
        Assert.assertEquals(State.TIMED_WAITING, thread.getState());
        synchronized (ThreadStateTest.class) {
            Thread.sleep(10);
            Assert.assertEquals(State.BLOCKED, thread.getState());
        }
        Thread.sleep(5);
        Assert.assertEquals(State.TERMINATED, thread.getState());
    }

    @Test
    public void testTimedWait_by_notified() throws InterruptedException {
        Thread thread = new Thread(ThreadStateTest::timedWait);
        thread.start();
        Thread.sleep(5);
        Assert.assertEquals(State.TIMED_WAITING, thread.getState());
        synchronized (ThreadStateTest.class) {
            ThreadStateTest.class.notify();
            Assert.assertEquals(State.BLOCKED, thread.getState());
        }
        Thread.sleep(5);
        Assert.assertEquals(State.TERMINATED, thread.getState());
    }

    @Test
    public void testSleep() throws InterruptedException {
        Thread thread = new Thread(ThreadStateTest::sleep);
        thread.start();
        Thread.sleep(5);
        Assert.assertEquals(State.TIMED_WAITING, thread.getState());
    }

    private void printThreadStates(List<Thread> threads) {
        System.out.println("----- Current Thread States -----");
        threads.stream().map(t -> t.getName() + " State: " + t.getState()).forEach(System.out::println);
    }

    @SneakyThrows
    private static synchronized void run() {
        ThreadStateTest.class.wait();
    }

    @SneakyThrows
    private static synchronized void timedWait() {
        ThreadStateTest.class.wait(10);
    }

    @SneakyThrows
    private static void sleep() {
        Thread.sleep(Integer.MAX_VALUE);
    }

}
